'''
Created on 10/05/2009

@author: nirg
'''
import sys, os
sys.path.insert(0, os.getcwdu().split(os.sep+"GMT"+os.sep)[0])

import datetime
import logging
import messageinfo
import re
import sys

import jwzthreading

import mail
import stats.base
import stats.bucket
import stats.group
import stats.table
import threading

from dateutil.relativedelta import relativedelta


class Miner(object):
    '''Main class to do gmail minning.'''
    def __init__(self):
        self._lock = threading.Lock()
        
    def mine(self, username, password, since_date=None):
        results = {}
        logging.info("Initializing")
        opts = self.GetOptsMap(username, password)
        try:
            message_infos = self.GetMessageInfos(opts, since_date)
            
            logging.info("Extracting threads")
            threads = self.ExtractThreads(message_infos)
            stats = self.InitStats(messageinfo.MessageInfo.GetDateRange())
            
            logging.info("Generating stats")
            for stat in stats:
                stat.ProcessMessageInfos(message_infos, threads)
            
            logging.info("Outputting...")
            
            for stat in stats:
                results[stat.GetTitle()] = stat.GetJSon()
        except Exception, exp:
            logging.info("Mining raised the following exception %s\n%s" % (str(type(exp)), str(exp)))
               
        logging.info("Done")
        return results
        
    def GetOptsMap(self, username, password):
        '''
        Gets a dictionary of desired options. Username is expected with trailling 'gmail.com'
        ''' 
        return {"server": "imap.gmail.com", \
                "username": username, \
                "me": username, \
                "password": password, \
                "use_ssl": None, \
                #"max_messages": 1000, \
                "skip_labels": None}
        
    def GetMessageInfos(self, opts, since_date):
        self._lock.acquire()
        try:
            m = mail.Mail(
                    opts["server"], "use_ssl" in opts, opts["username"], opts["password"],
                    "record" in opts, "replay" in opts, 
                    "max_messages" in opts and int(opts["max_messages"]) or -1,
                    "random_subset" in opts)
            
            # First, get all message infos
            m.SelectAllMail()
            
            # revert to commented version
    #        message_infos = m.GetMessageInfos(datetime.date.today()+relativedelta(months=-1))
            message_infos = m.GetMessageInfos(since_date)
            
            # Then for each mailbox, see which messages are in it, and attach that to 
            # the mail info
            if "skip_labels" not in opts:
                message_infos_by_id = \
                        dict([(mi.GetMessageId(), mi) for mi in message_infos])
                
                # Don't want to parse all these dates, since we already have them from the
                # message infos above.
                messageinfo.MessageInfo.SetParseDate(False)
                
                for mailbox in m.GetMailboxes():
                    m.SelectMailbox(mailbox)
                    message_ids = m.GetMessageIds()
                    for mid in message_ids:
                        if mid in message_infos_by_id:
                            message_info = message_infos_by_id[mid]
                            message_info.AddMailbox(mailbox)
            
                messageinfo.MessageInfo.SetParseDate(True)
        
            m.Logout()
        except Exception:
            del m
            self._lock.release()
            raise
        
        self._lock.release()
        
        # Filter out those that we're not interested in
        if "filter_out" in opts:
            message_infos = self.FilterMessageInfos(message_infos, opts["filter_out"])
        
        # Tag messages as being from the user running the script
        if "me" in opts:
            logging.info("Identifying \"me\" messages")
            me_addresses = [
                    address.lower().strip() for address in opts["me"].split(",")]
            
            me_from_count = 0
            me_to_count = 0
            
            for message_info in message_infos:
                name, address = message_info.GetSender()
                
                for me_address in me_addresses:
                    if me_address == address:
                        message_info.is_from_me = True
                        me_from_count += 1
                        break
                        
                for name, address in message_info.GetRecipients():
                    for me_address in me_addresses:
                        if me_address == address:
                            message_info.is_to_me = True
                            me_to_count += 1
                            break
                    if message_info.is_to_me: break
                            
            logging.info("    %d messages are from \"me\"" % me_from_count)
            logging.info("    %d messages are to \"me\"" % me_to_count)
        
        return message_infos
    
    def FilterMessageInfos(self, message_infos, filter_param):
        logging.info("Filtering messages")
        remaining_message_infos = []
        
        filters = []
        raw_filters = filter_param.split(",")
        for raw_filter in raw_filters:
            operator, value = raw_filter.strip().split(":", 1)
            filters.append([operator, value.lower()])
        
        for message_info in message_infos:
            filtered_out = False
            for operator, operator_value in filters:
                if operator == "to":
                    pairs = message_info.GetRecipients()
                elif operator == "from":
                    pairs = [message_info.GetSender()]
                elif operator == "list":
                    pairs = [message_info.GetListId()]
                else:
                    raise AssertionError("unknown operator: %s" % operator)
    
                values = [name and name.lower() or "" for name, address in pairs] + \
                                 [address and address.lower() or "" for name, address in pairs]
    
                for value in values:
                    if value.find(operator_value) != -1:
                        filtered_out = True
                        break
                
                if filtered_out:
                    break
            
            if not filtered_out:
                remaining_message_infos.append(message_info)
    
        logging.info("    %d messages remaining" % len(remaining_message_infos))
        return remaining_message_infos
    
    def ExtractThreads(self, message_infos):
        thread_messages = []
        for message_info in message_infos:
            thread_message = jwzthreading.make_message(message_info.headers)
            if thread_message:
                thread_message.message_info = message_info
                thread_messages.append(thread_message)
                
        thread_dict = jwzthreading.thread(thread_messages)
        
        containers = []
        for subject, container in thread_dict.items():
            # jwzthreading is too aggressive in threading by subject and will combine
            # distinct threads that happen to have the same subject. Split them up if
            # we have a dummy container that has lots of children at the first level.
            if container.is_dummy() and len(container.children) >= 10:
                for child_container in container.children:
                    child_container.subject = subject
                    containers.append(child_container)
            else:
                container.subject = subject
                containers.append(container)
            
        return containers
    
    def InitStats(self, date_range):
        s = [
            stats.table.SenderTableStat(), 
            stats.table.RecipientTableStat(),
            stats.table.SizeTableStat(),
            stats.table.TimeOfDayStat()]
#            stats.base.TitleStat(date_range),
#            stats.group.StatTabGroup(
#                (
#                    "Time",
#                    stats.group.StatColumnGroup(
#                        stats.bucket.DayOfWeekStat(),
#                        stats.bucket.TimeOfDayStat(),
#                        stats.bucket.YearStat(date_range),
#                    ),
#                    stats.group.StatColumnGroup(
#                        stats.group.MonthStatCollection(date_range),
#                        stats.group.DayStatCollection(date_range),
#                    ),
#                ),
#                (
#                    "Size",
#                    stats.group.StatColumnGroup(
#                        stats.bucket.SizeBucketStat(),
#                        stats.table.SizeTableStat(),
#                    ),
#                ),
#                (
#                    "People and Lists",
#                    stats.group.StatColumnGroup(
#                        stats.table.SenderTableStat(),
#                        stats.group.SenderDistributionStatCollection(date_range),
#                    ),
#                    stats.group.StatColumnGroup(
#                        stats.table.RecipientTableStat(),
#                        stats.group.RecipientDistributionStatCollection(date_range),
#                    ),
#                    stats.group.StatColumnGroup(
#                        stats.table.ListIdTableStat(),
#                        stats.group.ListDistributionStatCollection(date_range),
#                    ),
#                ),
#                (
#                    "Me",
#                    stats.group.StatColumnGroup(
#                        stats.table.MeRecipientTableStat(),
#                        stats.group.MeRecipientDistributionStatCollection(date_range),                    
#                    ),
#                    stats.group.StatColumnGroup(
#                        stats.table.MeSenderTableStat(),
#                        stats.group.MeSenderDistributionStatCollection(date_range),                    
#                    ),
#                ),
#                (
#                    "Threads",
#                    stats.group.StatColumnGroup(
#                        stats.bucket.ThreadSizeBucketStat(),
#                        stats.table.ThreadSizeTableStat(),
#                    ),
#                    stats.group.StatColumnGroup(
#                        stats.table.ThreadStarterTableStat(),
#                        stats.table.ThreadListTableStat(),
#                    )
#                )
#            )
#        ]
        
        return s
    
if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG, format="[%(asctime)s] %(message)s")
    miner = Miner()
    import getpass
    import simplejson
    stats = miner.mine(sys.argv[1], getpass.getpass(), datetime.datetime.now()-relativedelta(days=1))
    print simplejson.dumps(stats, indent=0)
